// Package db db/checksum_result_model.go 校验结果相关模型和数据库操作
package db

import (
	"context"      // 用于带超时的数据库查询
	"database/sql" // 标准数据库接口
	"errors"       // 用于创建标准错误
	"fmt"          // 用于格式化错误信息和日志
	"strings"      // 用于字符串操作
	"time"         // 用于处理时间戳

	"px-table-checksum-go/models" // 导入项目定义的标准模型

	log "github.com/sirupsen/logrus" // 导入日志库
)

// ChecksumResult 存储单个 chunk 的校验结果 (数据库交互层结构)
// 注意：这个结构体主要用于与数据库的 t_checksum 表交互。
// 它与 models.ChecksumResult 有细微差别（例如字段名、类型），通过转换函数保持一致。
type ChecksumResult struct {
	DBID       string    `db:"f_dbid"`        // 数据库标识 (host:port)
	SchemaName string    `db:"f_schema_name"` // 库名
	TableName  string    `db:"f_table_name"`  // 表名
	ChunkNo    int       `db:"f_chunk_no"`    // Chunk 编号 (从1开始)
	CreateTime time.Time `db:"f_create_time"` // 记录创建时间 (数据库自动生成或插入时设置)
	MinKey     string    `db:"f_min_key"`     // Chunk 起始键值 (多列用逗号连接，兼容 ParseKeyFromString)
	MaxKey     string    `db:"f_max_key"`     // Chunk 结束键值 (chunk内最后一条记录的键，多列用逗号连接)
	ChunkCRC32 uint32    `db:"f_chunk_crc32"` // Chunk CRC32 校验和 (注意：数据库可能是 BIGINT UNSIGNED)
	RowsCount  uint      `db:"f_rows_count"`  // Chunk 内的行数 (可选, 注意：数据库可能是 INT UNSIGNED)
}

// ConvertToModelResult 将数据库交互层的 ChecksumResult 转换为标准的 models.ChecksumResult
// 这是为了在业务逻辑层使用统一的模型。
func (r *ChecksumResult) ConvertToModelResult() *models.ChecksumResult {
	if r == nil {
		return nil // 处理 nil 输入
	}
	// 进行结构体字段映射
	return &models.ChecksumResult{
		DBID:       r.DBID,
		SchemaName: r.SchemaName,
		TableName:  r.TableName,
		ChunkNo:    r.ChunkNo,
		CRC32:      r.ChunkCRC32, // 直接映射
		MinKey:     r.MinKey,
		MaxKey:     r.MaxKey,
		RowCount:   int(r.RowsCount), // 将 uint 转换为 int
		CreateTime: r.CreateTime,
	}
}

// ConvertFromModelResult 将标准的 models.ChecksumResult 转换为数据库交互层的 ChecksumResult
// 用于将业务逻辑层的模型转换为可写入数据库的结构。
func ConvertFromModelResult(mr *models.ChecksumResult) *ChecksumResult {
	if mr == nil {
		return nil // 处理 nil 输入
	}
	// 进行结构体字段映射
	return &ChecksumResult{
		DBID:       mr.DBID,
		SchemaName: mr.SchemaName,
		TableName:  mr.TableName,
		ChunkNo:    mr.ChunkNo,
		CreateTime: mr.CreateTime,
		MinKey:     mr.MinKey,
		MaxKey:     mr.MaxKey,
		ChunkCRC32: mr.CRC32,          // 直接映射
		RowsCount:  uint(mr.RowCount), // 将 int 转换为 uint
	}
}

// GetChecksumResults 从数据库获取指定表的所有校验结果 (公共接口)
// 返回标准模型切片。
func GetChecksumResults(dbID, schemaName, tableName string) ([]*models.ChecksumResult, error) {
	logger := log.WithFields(log.Fields{
		"DBID":   dbID,
		"Schema": schemaName,
		"Table":  tableName,
		"func":   "GetChecksumResults",
	})
	logger.Info("开始获取校验结果")

	// 参数验证
	if dbID == "" || schemaName == "" || tableName == "" {
		logger.Error("参数无效: DBID, Schema 或 Table 为空")
		return nil, errors.New("获取校验结果的参数无效")
	}

	// 调用内部函数从数据库获取结果
	dbResults, err := getChecksumResultsInternal(dbID, schemaName, tableName)
	if err != nil {
		// 内部函数已记录错误日志
		return nil, err // 直接返回错误
	}

	// 将数据库模型转换为标准模型
	modelResults := make([]*models.ChecksumResult, len(dbResults))
	for i, dbResult := range dbResults {
		modelResults[i] = dbResult.ConvertToModelResult() // 调用转换函数
	}

	logger.Infof("成功获取校验结果，共 %d 条记录", len(modelResults))
	return modelResults, nil
}

// getChecksumResultsInternal 内部实现，从数据库查询校验结果 (私有)
// 返回数据库交互层模型切片。
func getChecksumResultsInternal(dbID, schemaName, tableName string) ([]*ChecksumResult, error) {
	logger := log.WithFields(log.Fields{
		"DBID":   dbID,
		"Schema": schemaName,
		"Table":  tableName,
		"func":   "getChecksumResultsInternal",
	})
	logger.Debug("开始执行内部校验结果获取")

	// 获取校验数据库连接
	checksumDB := GetDBManager().GetChecksumDB() // 假设 GetChecksumDB() 返回 *sql.DB
	if checksumDB == nil {
		logger.Error("Checksum数据库连接为空")
		return nil, errors.New("Checksum 数据库连接无效")
	}

	// （可选）检查数据库连接是否活跃
	if err := checksumDB.Ping(); err != nil {
		logger.Errorf("Checksum数据库连接无法ping通: %v", err)
		return nil, fmt.Errorf("Checksum数据库连接无法使用: %w", err)
	}

	// 构建查询语句
	query := `
		SELECT f_chunk_no, f_min_key, f_max_key, f_chunk_crc32, f_rows_count, f_create_time
		FROM t_checksum 
		WHERE f_dbid = ? AND f_schema_name = ? AND f_table_name = ?
		ORDER BY f_chunk_no /* 确保按块顺序返回 */
	`

	// 记录将要执行的 SQL 和参数
	logger.Debugf("执行查询: %s", strings.ReplaceAll(query, "\n", " "))
	logger.Debugf("查询参数: [%s, %s, %s]", dbID, schemaName, tableName)

	// 创建带超时的上下文，防止查询永久阻塞
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // 30秒超时
	defer cancel()                                                           // 确保上下文被取消

	// 执行查询
	rows, err := checksumDB.QueryContext(ctx, query, dbID, schemaName, tableName)
	if err != nil {
		logger.Errorf("查询t_checksum表失败: %v", err)
		return nil, fmt.Errorf("查询校验结果失败: %w", err)
	}
	// 确保结果集在使用后关闭
	defer rows.Close()

	var results []*ChecksumResult // 存储查询结果
	resultCount := 0              // 记录处理的总行数
	warningCount := 0             // 记录处理过程中的警告数 (例如 NULL 值)

	logger.Debug("开始处理查询结果集")
	// 遍历查询结果的每一行
	for rows.Next() {
		resultCount++ // 增加行计数

		// 定期检查上下文是否被取消
		if resultCount%100 == 0 { // 每处理100行检查一次
			select {
			case <-ctx.Done():
				logger.Warnf("处理结果集时操作被取消: %v", ctx.Err())
				return nil, fmt.Errorf("处理校验结果被取消: %w", ctx.Err()) // 返回错误
			default:
				// 继续处理
			}
		}

		var result ChecksumResult // 用于存储当前行的数据
		// 定义用于接收可能为 NULL 的数据库字段的变量
		var crc32Val sql.NullInt64 // CRC32 在数据库中可能是 BIGINT
		var rowCount sql.NullInt64 // RowsCount 在数据库中可能是 INT
		var createTime sql.NullTime

		// 设置已知字段
		result.DBID = dbID
		result.SchemaName = schemaName
		result.TableName = tableName

		// 扫描当前行的数据到变量中
		err := rows.Scan(
			&result.ChunkNo, // f_chunk_no (INT UNSIGNED NOT NULL)
			&result.MinKey,  // f_min_key (VARCHAR NOT NULL)
			&result.MaxKey,  // f_max_key (VARCHAR NOT NULL)
			&crc32Val,       // f_chunk_crc32 (BIGINT UNSIGNED NULL) -> NullInt64
			&rowCount,       // f_rows_count (INT UNSIGNED NULL) -> NullInt64
			&createTime,     // f_create_time (TIMESTAMP NOT NULL) -> NullTime
		)
		if err != nil {
			logger.Errorf("扫描校验结果行失败 (第 %d 行): %v", resultCount, err)
			return nil, fmt.Errorf("扫描校验结果失败: %w", err)
		}

		// 处理可能为 NULL 的字段
		if crc32Val.Valid {
			// 注意：数据库是 BIGINT UNSIGNED，这里需要安全转换为 uint32
			// 如果 crc32Val.Int64 超出 uint32 范围，会发生截断，可能导致问题
			// 更好的做法是数据库设计时就用 INT UNSIGNED 或 BIGINT (有符号)
			// 假设值不会超过 uint32 最大值
			result.ChunkCRC32 = uint32(crc32Val.Int64)
		} else {
			logger.Warnf("ChunkNo=%d 的 CRC32 值为 NULL，使用默认值 0", result.ChunkNo)
			result.ChunkCRC32 = 0 // 如果数据库值为 NULL，CRC32 设为 0
			warningCount++
		}

		if rowCount.Valid {
			// 数据库是 INT UNSIGNED，转换为 Go 的 uint
			// 如果 rowCount.Int64 为负数，转换为 uint 会有问题，但数据库类型是 UNSIGNED
			result.RowsCount = uint(rowCount.Int64)
		} else {
			logger.Warnf("ChunkNo=%d 的 RowCount 值为 NULL，使用默认值 0", result.ChunkNo)
			result.RowsCount = 0 // 如果数据库值为 NULL，行数设为 0
			warningCount++
		}

		if createTime.Valid {
			result.CreateTime = createTime.Time // 使用数据库中的时间
		} else {
			// 理论上 CreateTime 不应为 NULL (NOT NULL DEFAULT CURRENT_TIMESTAMP)
			// 但为了健壮性，如果为 NULL，则使用当前时间
			logger.Debugf("ChunkNo=%d 的 CreateTime 值为 NULL，使用当前时间", result.ChunkNo)
			result.CreateTime = time.Now()
		}

		// 记录处理的部分结果日志（避免日志过多）
		if resultCount <= 5 || resultCount%100 == 0 {
			logger.Debugf("处理校验结果: ChunkNo=%d, MinKey=%s, MaxKey=%s, CRC32=%d, Rows=%d",
				result.ChunkNo, result.MinKey, result.MaxKey, result.ChunkCRC32, result.RowsCount)
		}

		// 将处理好的结果添加到结果切片中
		results = append(results, &result)
	} // 结束 rows.Next()

	// 检查遍历过程中是否发生错误
	if err = rows.Err(); err != nil {
		logger.Errorf("遍历结果集时出错: %v", err)
		return nil, fmt.Errorf("遍历校验结果失败: %w", err)
	}

	// 记录最终的查询统计信息
	logger.Infof("查询完成: 处理了 %d 行数据，获取到 %d 条校验记录，有 %d 个警告",
		resultCount, len(results), warningCount)

	// 如果没有找到任何结果，记录警告
	if len(results) == 0 {
		logger.Warnf("表 %s.%s (DBID=%s) 未找到任何校验结果", schemaName, tableName, dbID)
	}

	return results, nil
}
